/*
REWRITTEN FROM SCRATCH BY XINEF, IT OWNS NOW!
*/

#include "ScriptMgr.h"
#include "ScriptedCreature.h"
#include "SpellScript.h"
#include "GridNotifiers.h"
#include "CombatAI.h"
#include "naxxramas.h"

enum Yells
{
    SAY_SPEECH                  = 0,
    SAY_KILL                    = 1,
    SAY_DEATH                   = 2,
    SAY_TELEPORT                = 3
};

enum Spells
{
    SPELL_HARVEST_SOUL				= 28679,
    SPELL_SHADOW_BOLT_10			= 29317,
    SPELL_SHADOW_BOLT_25			= 56405,
    SPELL_INFORM_LIVING_TRAINEE		= 27892,
    SPELL_INFORM_LIVING_KNIGHT		= 27928,
    SPELL_INFORM_LIVING_RIDER		= 27935,
    SPELL_INFORM_DEAD_TRAINEE		= 27915,
    SPELL_INFORM_DEAD_KNIGHT		= 27931,
    SPELL_INFORM_DEAD_RIDER			= 27937,

	SPELL_SHADOW_MARK				= 27825,

	/* living trainee spells */
	SPELL_DEATH_PLAGUE = 55604,

	/* living rider spells */
	SPELL_SHADOW_BOLT_VOLLEY = 27831,

	/* spectral trainee spells */
	SPELL_ARCANE_EXPLOSION = 27989,

	/* spectral knight spells */
	SPELL_WHIRLWIND = 56408,

	/* spectral rider spells */
	SPELL_DRAIN_LIFE = 27994,
	SPELL_UNHOLY_FRENZY = 55648,

	/* spectral horse spells */
	SPELL_STOMP = 27993,

	SPELL_ANCHOR_2_TRAINEE = 27893,
	SPELL_ANCHOR_2_DK = 27929,
	SPELL_ANCHOR_2_RIDER = 27936,

	/* teleport spells */
	SPELL_TELEPORT_DEAD = 28025,
	SPELL_TELEPORT_LIVE = 28026
};

enum Misc
{
    NPC_LIVING_TRAINEE				= 16124,
    NPC_LIVING_KNIGHT				= 16125,
    NPC_LIVING_RIDER				= 16126,
    NPC_DEAD_TRAINEE				= 16127,
    NPC_DEAD_KNIGHT					= 16148,
	NPC_DEAD_HORSE					= 16149,
    NPC_DEAD_RIDER					= 16150,
	NPC_GOTHIK                      = 16060,
	NPC_TRIGGER                     = 16137,

	ACTION_GATE_OPEN				= 1,
};

enum Events
{
    EVENT_SUMMON_ADDS				= 1,
    EVENT_SPELL_HARVEST_SOUL		= 2,
    EVENT_SPELL_SHADOW_BOLT			= 3,
    EVENT_TELEPORT					= 4,
	EVENT_CHECK_HEALTH				= 5,
	EVENT_CHECK_PLAYERS				= 6,
};

const uint32 gothikWaves[24][2] =
{
    {NPC_LIVING_TRAINEE,	20000},
    {NPC_LIVING_TRAINEE,	20000},
    {NPC_LIVING_TRAINEE,	10000},
    {NPC_LIVING_KNIGHT,		10000},
    {NPC_LIVING_TRAINEE,	15000},
    {NPC_LIVING_KNIGHT,		10000},
    {NPC_LIVING_TRAINEE,	15000},
    {NPC_LIVING_TRAINEE,	0},
    {NPC_LIVING_KNIGHT,		10000},
    {NPC_LIVING_RIDER,		10000},
    {NPC_LIVING_TRAINEE,	5000},
    {NPC_LIVING_KNIGHT,		15000},
    {NPC_LIVING_RIDER,		0},
    {NPC_LIVING_TRAINEE,	10000},
    {NPC_LIVING_KNIGHT,		10000},
    {NPC_LIVING_TRAINEE,	10000},
    {NPC_LIVING_RIDER,		5000},
    {NPC_LIVING_KNIGHT,		5000},
    {NPC_LIVING_TRAINEE,	20000},
    {NPC_LIVING_RIDER,		0},
    {NPC_LIVING_KNIGHT,		0},
    {NPC_LIVING_TRAINEE,	15000},
    {NPC_LIVING_TRAINEE,	29000},
    {0, 0}
};

const Position PosSummonLiving[6] =
{
    {2669.7f, -3428.76f, 268.56f, 1.6f},
    {2692.1f, -3428.76f, 268.56f, 1.6f},
    {2714.4f, -3428.76f, 268.56f, 1.6f},
    {2669.7f, -3431.67f, 268.56f, 1.6f},
    {2692.1f, -3431.67f, 268.56f, 1.6f},
    {2714.4f, -3431.67f, 268.56f, 1.6f},
};

const Position PosSummonDead[5] =
{
    {2725.1f, -3310.0f, 268.85f, 3.4f},
    {2699.3f, -3322.8f, 268.60f, 3.3f},
    {2733.1f, -3348.5f, 268.84f, 3.1f},
    {2682.8f, -3304.2f, 268.85f, 3.9f},
    {2664.8f, -3340.7f, 268.23f, 3.7f},
};

const Position PosGroundLivingSide = {2691.2f, -3387.0f, 267.68f, 1.52f};
const Position PosGroundDeadSide   = {2693.5f, -3334.6f, 267.68f, 4.67f};
const Position PosPlatform         = {2640.5f, -3360.6f, 285.26f, 0.0f};

#define POS_Y_GATE  -3360.78f
#define POS_Y_WEST  -3285.0f
#define POS_Y_EAST  -3434.0f
#define POS_X_NORTH  2750.49f
#define POS_X_SOUTH  2633.84f
#define IN_LIVE_SIDE(who) (who->GetPositionY() < POS_Y_GATE)

// Predicate function to check that the r   efzr unit is NOT on the same side as the source.
struct NotOnSameSide : public std::unary_function<Unit *, bool>
{
    bool m_inLiveSide;
    NotOnSameSide(Unit *pSource) : m_inLiveSide(IN_LIVE_SIDE(pSource)) {}
    
	bool operator() (const Unit *pTarget)
	{
		return (m_inLiveSide != IN_LIVE_SIDE(pTarget));
    }
};

const uint32 CGUID_TRIGGER = 127618;

class boss_gothik : public CreatureScript
{
public:
    boss_gothik() : CreatureScript("boss_gothik") { }

    CreatureAI* GetAI(Creature* pCreature) const
    {
        return new boss_gothikAI (pCreature);
    }

	struct boss_gothikAI : public ScriptedAI
	{
		boss_gothikAI(Creature *c) : ScriptedAI(c), summons(me)
		{
			pInstance = me->GetInstanceScript();
		}

		EventMap events;
		SummonList summons;
		InstanceScript* pInstance;
		bool secondPhase;
		bool gateOpened;
		uint8 waveCount;

		bool IsInRoom()
		{
			if (me->GetPositionX() > 2767 || me->GetPositionX() < 2618 || me->GetPositionY() > -3285 || me->GetPositionY() < -3435)
			{
				EnterEvadeMode();
				return false;
			}

			return true;
		}

		void Reset()
		{
			events.Reset();
			summons.DespawnAll();
			me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE|UNIT_FLAG_IMMUNE_TO_PC|UNIT_FLAG_DISABLE_MOVE);
			me->SetReactState(REACT_PASSIVE);
			secondPhase = false;
			gateOpened = false;
			waveCount = 0;

			if (pInstance)
			{
				pInstance->SetData(EVENT_GOTHIK, NOT_STARTED);
				if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetData64(DATA_GOTHIK_ENTER_GATE)))
					go->SetGoState(GO_STATE_ACTIVE);
				if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetData64(DATA_GOTHIK_INNER_GATE)))
					go->SetGoState(GO_STATE_ACTIVE);
				if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetData64(DATA_GOTHIK_EXIT_GATE)))
					go->SetGoState(GO_STATE_READY);
			}
		}

		void EnterCombat(Unit *who)
		{
			me->SetInCombatWithZone();
			Talk(SAY_SPEECH);
			me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE|UNIT_FLAG_DISABLE_MOVE);
			me->NearTeleportTo(PosPlatform.GetPositionX(), PosPlatform.GetPositionY(), PosPlatform.GetPositionZ(), PosPlatform.GetOrientation());
			
			events.ScheduleEvent(EVENT_SUMMON_ADDS, 30000);
			events.ScheduleEvent(EVENT_CHECK_PLAYERS, 120000);
			
			if (pInstance)
			{
				pInstance->SetData(EVENT_GOTHIK, IN_PROGRESS);
				if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetData64(DATA_GOTHIK_ENTER_GATE)))
					go->SetGoState(GO_STATE_READY);
				if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetData64(DATA_GOTHIK_INNER_GATE)))
					go->SetGoState(GO_STATE_READY);
			}
		}

		void JustSummoned(Creature *summon)
		{
			if (gateOpened)
				summon->AI()->DoAction(ACTION_GATE_OPEN);
				
			summons.Summon(summon);
			summon->SetInCombatWithZone();
		}

		void SummonedCreatureDespawn(Creature* cr) { summons.Despawn(cr); }

		void KilledUnit(Unit* who)
		{
			if (who->GetTypeId() != TYPEID_PLAYER)
				return;

			if (!urand(0,3))
				Talk(SAY_KILL);

			if (pInstance)
				pInstance->SetData(DATA_IMMORTAL_FAIL, 0);
		}

		void JustDied(Unit* Killer)
		{
			Talk(SAY_DEATH);
			summons.DespawnAll();

			if (pInstance)
			{
				pInstance->SetData(EVENT_GOTHIK, DONE);
				if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetData64(DATA_GOTHIK_ENTER_GATE)))
					go->SetGoState(GO_STATE_ACTIVE);
				if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetData64(DATA_GOTHIK_INNER_GATE)))
					go->SetGoState(GO_STATE_ACTIVE);
				if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetData64(DATA_GOTHIK_EXIT_GATE)))
					go->SetGoState(GO_STATE_ACTIVE);
			}
		}

		void SummonHelpers(uint32 entry)
		{
			switch(entry)
			{
				case NPC_LIVING_TRAINEE:
					me->SummonCreature(NPC_LIVING_TRAINEE, PosSummonLiving[0].GetPositionX(), PosSummonLiving[0].GetPositionY(), PosSummonLiving[0].GetPositionZ(), PosSummonLiving[0].GetOrientation());
					me->SummonCreature(NPC_LIVING_TRAINEE, PosSummonLiving[1].GetPositionX(), PosSummonLiving[1].GetPositionY(), PosSummonLiving[1].GetPositionZ(), PosSummonLiving[1].GetOrientation());
					if (Is25ManRaid())
						me->SummonCreature(NPC_LIVING_TRAINEE, PosSummonLiving[2].GetPositionX(), PosSummonLiving[2].GetPositionY(), PosSummonLiving[2].GetPositionZ(), PosSummonLiving[2].GetOrientation());
					break;
				case NPC_LIVING_KNIGHT:
					me->SummonCreature(NPC_LIVING_KNIGHT, PosSummonLiving[3].GetPositionX(), PosSummonLiving[3].GetPositionY(), PosSummonLiving[3].GetPositionZ(), PosSummonLiving[3].GetOrientation());
					if (Is25ManRaid())
						me->SummonCreature(NPC_LIVING_KNIGHT, PosSummonLiving[5].GetPositionX(), PosSummonLiving[5].GetPositionY(), PosSummonLiving[5].GetPositionZ(), PosSummonLiving[5].GetOrientation());
					break;
				case NPC_LIVING_RIDER:
					me->SummonCreature(NPC_LIVING_RIDER, PosSummonLiving[4].GetPositionX(), PosSummonLiving[4].GetPositionY(), PosSummonLiving[4].GetPositionZ(), PosSummonLiving[4].GetOrientation());
					break;
			}
		}

		bool CheckGroupSplitted()
		{
			Map::PlayerList const &PlayerList = me->GetMap()->GetPlayers();
			if (!PlayerList.isEmpty())
			{
				bool checklife = false;
				bool checkdead = false;
				for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i)
				{
					Player* player = i->GetSource();
					if (player->IsAlive() &&
						player->GetPositionX() <= POS_X_NORTH &&
						player->GetPositionX() >= POS_X_SOUTH &&
						player->GetPositionY() <= POS_Y_GATE &&
						player->GetPositionY() >= POS_Y_EAST)
						checklife = true;
					else if (player->IsAlive() &&
						player->GetPositionX() <= POS_X_NORTH &&
						player->GetPositionX() >= POS_X_SOUTH &&
						player->GetPositionY() >= POS_Y_GATE &&
						player->GetPositionY() <= POS_Y_WEST)
						checkdead = true;

					if (checklife && checkdead)
						return true;
				}
			}
			return false;
		}

		void SpellHit(Unit *caster, const SpellInfo* spellInfo)
		{
			uint8 pos = urand(0,4);
			switch (spellInfo->Id)
			{
				case SPELL_INFORM_LIVING_TRAINEE:
					me->SummonCreature(NPC_DEAD_TRAINEE, PosSummonDead[pos].GetPositionX(), PosSummonDead[pos].GetPositionY(), PosSummonDead[pos].GetPositionZ(), PosSummonDead[pos].GetOrientation());
					break;
				case SPELL_INFORM_LIVING_KNIGHT:
					me->SummonCreature(NPC_DEAD_KNIGHT, PosSummonDead[pos].GetPositionX(), PosSummonDead[pos].GetPositionY(), PosSummonDead[pos].GetPositionZ(), PosSummonDead[pos].GetOrientation());
					break;
				case SPELL_INFORM_LIVING_RIDER:
					me->SummonCreature(NPC_DEAD_RIDER, PosSummonDead[pos].GetPositionX(), PosSummonDead[pos].GetPositionY(), PosSummonDead[pos].GetPositionZ(), PosSummonDead[pos].GetOrientation());
					me->SummonCreature(NPC_DEAD_HORSE, PosSummonDead[pos].GetPositionX(), PosSummonDead[pos].GetPositionY(), PosSummonDead[pos].GetPositionZ(), PosSummonDead[pos].GetOrientation());
					break;
			}

			me->HandleEmoteCommand(EMOTE_ONESHOT_SPELL_CAST);
		}

		void DamageTaken(Unit*, uint32 &damage, DamageEffectType, SpellSchoolMask)
		{
			if (!secondPhase)
				damage = 0;
		}

		void UpdateAI(uint32 diff)
		{
			if (!IsInRoom())
				return;

			if (!UpdateVictim())
				return;

			events.Update(diff);
			if (me->HasUnitState(UNIT_STATE_CASTING))
				return;

			switch (events.GetEvent())
			{
				case EVENT_SPELL_SHADOW_BOLT:
					me->CastSpell(me->GetVictim(), RAID_MODE(SPELL_SHADOW_BOLT_10, SPELL_SHADOW_BOLT_25), false);
					events.RepeatEvent(2000);
					break;
				case EVENT_SPELL_HARVEST_SOUL:
					me->CastSpell(me, SPELL_HARVEST_SOUL, false);
					events.RepeatEvent(15000);
					break;
				case EVENT_TELEPORT:
					me->AttackStop();
					if (IN_LIVE_SIDE(me))
						me->NearTeleportTo(PosGroundDeadSide.GetPositionX(), PosGroundDeadSide.GetPositionY(), PosGroundDeadSide.GetPositionZ(), PosGroundDeadSide.GetOrientation());
					else
						me->NearTeleportTo(PosGroundLivingSide.GetPositionX(), PosGroundLivingSide.GetPositionY(), PosGroundLivingSide.GetPositionZ(), PosGroundLivingSide.GetOrientation());

					me->getThreatManager().resetAggro(NotOnSameSide(me));
					if (Unit *pTarget = SelectTarget(SELECT_TARGET_NEAREST, 0))
					{
						me->getThreatManager().addThreat(pTarget, 100.0f);
						AttackStart(pTarget);
					}
					events.RepeatEvent(20000);
					break;
				case EVENT_CHECK_HEALTH:
					if (me->HealthBelowPct(30) && pInstance)
					{
						if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetData64(DATA_GOTHIK_INNER_GATE)))
							go->SetGoState(GO_STATE_ACTIVE);

						events.CancelEvent(EVENT_TELEPORT);
						events.PopEvent();
						break;
					}
					events.RepeatEvent(1000);
					break;
				case EVENT_SUMMON_ADDS:
					if (gothikWaves[waveCount][0])
					{
						SummonHelpers(gothikWaves[waveCount][0]);
						events.RepeatEvent(gothikWaves[waveCount][1]);
					}
					else
					{
						secondPhase = true;
						Talk(SAY_TELEPORT);
						me->NearTeleportTo(PosGroundLivingSide.GetPositionX(), PosGroundLivingSide.GetPositionY(), PosGroundLivingSide.GetPositionZ(), PosGroundLivingSide.GetOrientation());
						me->SetReactState(REACT_AGGRESSIVE);
						me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE|UNIT_FLAG_IMMUNE_TO_PC|UNIT_FLAG_DISABLE_MOVE);
						
						summons.DoAction(ACTION_GATE_OPEN);
						summons.DoZoneInCombat();
						events.ScheduleEvent(EVENT_SPELL_SHADOW_BOLT, 1000);
						events.ScheduleEvent(EVENT_SPELL_HARVEST_SOUL, urand(5000,15000));
						events.ScheduleEvent(EVENT_TELEPORT, 20000);
						events.ScheduleEvent(EVENT_CHECK_HEALTH, 1000);
						events.PopEvent();
					}

					waveCount++;
					break;
				case EVENT_CHECK_PLAYERS:
					if (!CheckGroupSplitted())
					{
						if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetData64(DATA_GOTHIK_INNER_GATE)))
							go->SetGoState(GO_STATE_ACTIVE);

						summons.DoAction(ACTION_GATE_OPEN);
						summons.DoZoneInCombat();
						gateOpened = true;
					}
					events.PopEvent();
					break;
			}

			DoMeleeAttackIfReady();
		}
	};
};

class npc_boss_gothik_minion : public CreatureScript
{
public:
    npc_boss_gothik_minion() : CreatureScript("npc_boss_gothik_minion") { }

    CreatureAI* GetAI(Creature* pCreature) const
    {
        return new npc_boss_gothik_minionAI (pCreature);
    }

	struct npc_boss_gothik_minionAI : public CombatAI
	{
		npc_boss_gothik_minionAI(Creature *c) : CombatAI(c)
		{
			livingSide = IN_LIVE_SIDE(me);
			gateOpened = false;
		}

		EventMap events;
		bool livingSide;
		bool gateOpened;

		bool IsOnSameSide(Unit const* who) const { return livingSide == IN_LIVE_SIDE(who); }
		bool CanAIAttack(Unit const* target) const { return gateOpened || IsOnSameSide(target); }

		void Reset() { events.Reset(); }
		void EnterCombat(Unit* who) { me->SetInCombatWithZone(); }
		void DamageTaken(Unit* attacker, uint32 &damage, DamageEffectType, SpellSchoolMask)
		{
			if (!attacker || (!gateOpened && !IsOnSameSide(attacker)))
				damage = 0;
		}

		void DoAction(int32 param)
		{
			if (param == ACTION_GATE_OPEN)
				gateOpened = true;
		}

		void JustDied(Unit *)
		{
			switch (me->GetEntry())
			{
				case NPC_LIVING_TRAINEE:
					me->CastSpell(me, SPELL_INFORM_LIVING_TRAINEE, true);
					break;
				case NPC_LIVING_KNIGHT:
					me->CastSpell(me, SPELL_INFORM_LIVING_KNIGHT, true);
					break;
				case NPC_LIVING_RIDER:
					me->CastSpell(me, SPELL_INFORM_LIVING_RIDER, true);
					break;
			}
		}

		void KilledUnit(Unit* who)
		{
			if (who->GetTypeId() == TYPEID_PLAYER && me->GetInstanceScript())
				me->GetInstanceScript()->SetData(DATA_IMMORTAL_FAIL, 0);
		}
	};

};

class npc_gothik_minion_livingtrainee : public CreatureScript
{
public:
	npc_gothik_minion_livingtrainee() : CreatureScript("npc_gothik_minion_livingtrainee") { }

	struct npc_gothik_minion_livingtraineeAI : public CombatAI
	{
		npc_gothik_minion_livingtraineeAI(Creature* creature) : CombatAI(creature), _deathPlagueTimer(urand(500, 2000)) { }

		void JustDied(Unit *)
		{
			switch (me->GetEntry())
			{
			case NPC_LIVING_TRAINEE:
				me->CastSpell(me, SPELL_INFORM_LIVING_TRAINEE, true);
				break;
			}
		}

		void _UpdateAI(uint32 diff)
		{
			if (diff < _deathPlagueTimer)
				_deathPlagueTimer -= diff;
			else
			{
				DoCastAOE(SPELL_DEATH_PLAGUE);
				_deathPlagueTimer = urand(500, 2000);
			}
			DoMeleeAttackIfReady();
		}
		uint32 _deathPlagueTimer;
	};

	CreatureAI* GetAI(Creature* creature) const
	{
		return GetInstanceAI<npc_gothik_minion_livingtraineeAI>(creature);
	}
};

class npc_gothik_minion_livingknight : public CreatureScript
{
public:
	npc_gothik_minion_livingknight() : CreatureScript("npc_gothik_minion_livingknight") { }

	struct npc_gothik_minion_livingknightAI : public CombatAI
	{
		npc_gothik_minion_livingknightAI(Creature* creature) : CombatAI(creature), _whirlwindTimer(urand(500, 1000)) { }

		void JustDied(Unit *)
		{
			switch (me->GetEntry())
			{
			case NPC_LIVING_KNIGHT:
				me->CastSpell(me, SPELL_INFORM_LIVING_KNIGHT, true);
				break;
			}
		}

		void _UpdateAI(uint32 diff)
		{
			if (diff < _whirlwindTimer)
				_whirlwindTimer -= diff;
			else
			{
				DoCastAOE(SPELL_SHADOW_MARK);
				_whirlwindTimer = urand(1500, 2000);
			}
			DoMeleeAttackIfReady();
		}
		uint32 _whirlwindTimer;
	};

	CreatureAI* GetAI(Creature* creature) const
	{
		return GetInstanceAI<npc_gothik_minion_livingknightAI>(creature);
	}
};

class npc_gothik_minion_livingrider : public CreatureScript
{
public:
	npc_gothik_minion_livingrider() : CreatureScript("npc_gothik_minion_livingrider") { }

	struct npc_gothik_minion_livingriderAI : public CombatAI
	{
		npc_gothik_minion_livingriderAI(Creature* creature) : CombatAI(creature), _boltVolleyTimer(urand(500, 1000)) { }

		void JustDied(Unit *)
		{
			switch (me->GetEntry())
			{
			case NPC_LIVING_RIDER:
				me->CastSpell(me, SPELL_INFORM_LIVING_RIDER, true);
				break;
			}
		}

		void _UpdateAI(uint32 diff)
		{
			if (diff < _boltVolleyTimer)
				_boltVolleyTimer -= diff;
			else
			{
				DoCastAOE(SPELL_SHADOW_BOLT_VOLLEY);
				_boltVolleyTimer = urand(1000, 1500);
			}
			if (!me->HasUnitState(UNIT_STATE_CASTING))
				DoMeleeAttackIfReady();
		}
		uint32 _boltVolleyTimer;
	};

	CreatureAI* GetAI(Creature* creature) const
	{
		return GetInstanceAI<npc_gothik_minion_livingriderAI>(creature);
	}
};

class npc_gothik_minion_spectraltrainee : public CreatureScript
{
public:
	npc_gothik_minion_spectraltrainee() : CreatureScript("npc_gothik_minion_spectraltrainee") { }

	struct npc_gothik_minion_spectraltraineeAI : public CombatAI
	{
		npc_gothik_minion_spectraltraineeAI(Creature* creature) : CombatAI(creature), _explosionTimer(2 * IN_MILLISECONDS) { }

		void _UpdateAI(uint32 diff)
		{
			if (diff < _explosionTimer)
				_explosionTimer -= diff;
			else
			{
				DoCastAOE(SPELL_ARCANE_EXPLOSION);
				_explosionTimer = 2 * IN_MILLISECONDS;
			}
			DoMeleeAttackIfReady();
		}
		uint32 _explosionTimer;
	};

	CreatureAI* GetAI(Creature* creature) const
	{
		return GetInstanceAI<npc_gothik_minion_spectraltraineeAI>(creature);
	}
};

class npc_gothik_minion_spectralknight : public CreatureScript
{
public:
	npc_gothik_minion_spectralknight() : CreatureScript("npc_gothik_minion_spectralknight") { }

	struct npc_gothik_minion_spectralknightAI : public CombatAI
	{
		npc_gothik_minion_spectralknightAI(Creature* creature) : CombatAI(creature), _whirlwindTimer(urand(1500, 2500)) { }

		void _UpdateAI(uint32 diff)
		{
			if (diff < _whirlwindTimer)
				_whirlwindTimer -= diff;
			else
			{
				DoCastAOE(SPELL_WHIRLWIND);
				_whirlwindTimer = urand(2000, 2500);
			}
			DoMeleeAttackIfReady();
		}
		uint32 _whirlwindTimer;
	};

	CreatureAI* GetAI(Creature* creature) const
	{
		return GetInstanceAI<npc_gothik_minion_spectralknightAI>(creature);
	}
};

class npc_gothik_minion_spectralrider : public CreatureScript
{
public:
	npc_gothik_minion_spectralrider() : CreatureScript("npc_gothik_minion_spectralrider") { }

	struct npc_gothik_minion_spectralriderAI : public CombatAI
	{
		npc_gothik_minion_spectralriderAI(Creature* creature) : CombatAI(creature), _frenzyTimer(urand(200, 500)), _drainTimer(urand(800, 1200)) { }

		void _UpdateAI(uint32 diff)
		{
			if (diff < _frenzyTimer)
				_frenzyTimer -= diff;
			else if (me->HasUnitState(UNIT_STATE_CASTING))
				_frenzyTimer = 0;
			else
			{ // target priority: knight > other rider > horse > gothik
				std::list<Creature*> DoFindFriendlyMissingBuff(float range, uint32 spellId);
				std::list<Creature*> potentialTargets = DoFindFriendlyMissingBuff(30.0f, SPELL_UNHOLY_FRENZY);
				Creature *knightTarget = nullptr, *riderTarget = nullptr, *horseTarget = nullptr, *gothikTarget = nullptr;
				for (Creature* pTarget : potentialTargets)
				{
					switch (pTarget->GetEntry())
					{
					case NPC_DEAD_KNIGHT:
						knightTarget = pTarget;
						break;
					case NPC_DEAD_RIDER:
						riderTarget = pTarget;
						break;
					case NPC_DEAD_HORSE:
						horseTarget = pTarget;
						break;
					case NPC_GOTHIK:
						gothikTarget = pTarget;
						break;
					}
					if (knightTarget)
						break;
				}
				Creature* target = knightTarget ? knightTarget : riderTarget ? riderTarget : horseTarget ? horseTarget : gothikTarget ? gothikTarget : nullptr;
				if (target)
					DoCast(target, SPELL_UNHOLY_FRENZY);
				_frenzyTimer = 20 * IN_MILLISECONDS;
			}

			if (diff < _drainTimer)
				_drainTimer -= diff;
			else
			{
				DoCastVictim(SPELL_DRAIN_LIFE);
				_drainTimer = urand(1000, 1500);
			}

			if (!me->HasUnitState(UNIT_STATE_CASTING))
				DoMeleeAttackIfReady();
		}
		uint32 _frenzyTimer, _drainTimer;
	};

	CreatureAI* GetAI(Creature* creature) const
	{
		return GetInstanceAI<npc_gothik_minion_spectralriderAI>(creature);
	}
};

class npc_gothik_minion_spectralhorse : public CreatureScript
{
public:
	npc_gothik_minion_spectralhorse() : CreatureScript("npc_gothik_minion_spectralhorse") { }

	struct npc_gothik_minion_spectralhorseAI : public CombatAI
	{
		npc_gothik_minion_spectralhorseAI(Creature* creature) : CombatAI(creature), _stompTimer(urand(1000, 1500)) { }

		void _UpdateAI(uint32 diff)
		{
			if (diff < _stompTimer)
				_stompTimer -= diff;
			else
			{
				DoCastAOE(SPELL_STOMP);
				_stompTimer = urand(1400, 1800);
			}
			DoMeleeAttackIfReady();
		}
		uint32 _stompTimer;
	};

	CreatureAI* GetAI(Creature* creature) const
	{
		return GetInstanceAI<npc_gothik_minion_spectralhorseAI>(creature);
	}
};

class spell_gothik_shadow_bolt_volley : public SpellScriptLoader
{
    public:
        spell_gothik_shadow_bolt_volley() : SpellScriptLoader("spell_gothik_shadow_bolt_volley") { }

        class spell_gothik_shadow_bolt_volley_SpellScript : public SpellScript
        {
            PrepareSpellScript(spell_gothik_shadow_bolt_volley_SpellScript);

            void FilterTargets(std::list<WorldObject*>& targets)
            {
                targets.remove_if(Trinity::UnitAuraCheck(false, SPELL_SHADOW_MARK));
            }

            void Register()
            {
                OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_gothik_shadow_bolt_volley_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY);
            }
        };

        SpellScript* GetSpellScript() const
        {
            return new spell_gothik_shadow_bolt_volley_SpellScript();
        }
};

class npc_gothik_trigger : public CreatureScript
{
public:
	npc_gothik_trigger() : CreatureScript("npc_gothik_trigger") { }

	struct npc_gothik_triggerAI : public ScriptedAI
	{
		npc_gothik_triggerAI(Creature* creature) : ScriptedAI(creature) { creature->SetDisableGravity(true); }

		void UpdateAI(uint32 /*diff*/) { }
		void EnterCombat(Unit* /*who*/) { }
		void DamageTaken(Unit* /*who*/, uint32& damage, DamageEffectType, SpellSchoolMask) { damage = 0; }

		Creature* SelectRandomSkullPile()
		{
			std::list<Creature*> triggers;
			me->GetCreatureListWithEntryInGrid(triggers, NPC_TRIGGER, 150.0f);
			uint32 targetDBGuid = CGUID_TRIGGER + urand(800, 1200); // CGUID+8 to CGUID+12 are the triggers for the skull piles on dead side
			for (Creature* trigger : triggers)
			if (trigger && trigger->GetRespawnRadius() == targetDBGuid)
				return trigger;

			return nullptr;
		}
		void SpellHit(Unit* /*caster*/, SpellInfo const* spell)
		{
			if (!spell)
				return;

			switch (spell->Id)
			{
			case SPELL_INFORM_LIVING_TRAINEE:
				DoCastAOE(SPELL_ANCHOR_2_TRAINEE, true);
				break;
			case SPELL_INFORM_LIVING_KNIGHT:
				DoCastAOE(SPELL_ANCHOR_2_DK, true);
				break;
			case SPELL_INFORM_LIVING_RIDER:
				DoCastAOE(SPELL_ANCHOR_2_RIDER, true);
				break;
			case SPELL_ANCHOR_2_TRAINEE:
				if (Creature* target = SelectRandomSkullPile())
					DoCast(target, SPELL_INFORM_DEAD_TRAINEE, true);
				break;
			case SPELL_ANCHOR_2_DK:
				if (Creature* target = SelectRandomSkullPile())
					DoCast(target, SPELL_INFORM_DEAD_KNIGHT, true);
				break;
			case SPELL_ANCHOR_2_RIDER:
				if (Creature* target = SelectRandomSkullPile())
					DoCast(target, SPELL_INFORM_DEAD_RIDER, true);
				break;
			case SPELL_INFORM_DEAD_TRAINEE:
				DoSummon(NPC_DEAD_TRAINEE, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN);
				break;
			case SPELL_INFORM_DEAD_KNIGHT:
				DoSummon(NPC_DEAD_KNIGHT, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN);
				break;
			case SPELL_INFORM_DEAD_RIDER:
				DoSummon(NPC_DEAD_RIDER, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN);
				DoSummon(NPC_DEAD_HORSE, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN);
				break;
			}
		}

		// dead side summons are "owned" by gothik
		void JustSummoned(Creature* summon)
		{
			if (Creature* gothik = ObjectAccessor::GetCreature(*me, me->GetInstanceScript()->GetData64(EVENT_GOTHIK)))
				gothik->AI()->JustSummoned(summon);
		}
		void SummonedCreatureDespawn(Creature* summon)
		{
			if (Creature* gothik = ObjectAccessor::GetCreature(*me, me->GetInstanceScript()->GetData64(EVENT_GOTHIK)))
				gothik->AI()->SummonedCreatureDespawn(summon);
		}
	};

	CreatureAI* GetAI(Creature* creature) const
	{
		return GetInstanceAI<npc_gothik_triggerAI>(creature);
	}
};

void AddSC_boss_gothik()
{
    new boss_gothik();
	new npc_gothik_minion_livingtrainee();
	new npc_gothik_minion_livingknight();
	new npc_gothik_minion_livingrider();
	new npc_gothik_minion_spectraltrainee();
	new npc_gothik_minion_spectralknight();
	new npc_gothik_minion_spectralrider();
	new npc_gothik_minion_spectralhorse();
	new npc_gothik_trigger();
    new npc_boss_gothik_minion();
    new spell_gothik_shadow_bolt_volley();
}
